#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <math.h>
#include <sys/time.h>

#include <mpi.h>

#define DEBUG 0

#define PHI (24.0f/100.0f)

enum times {START, END, DIFF};

void usage(void)
{
	printf("Syntax Error!\n");
	printf("Usage: ./heatdist <iterations> <size> <heat value>[opt]\n");
}

/* Function: calcRuntime() */
unsigned long calcRuntime(struct timeval *t)
{
	/* calc time difference */
	t[DIFF].tv_sec = t[END].tv_sec - t[START].tv_sec;
	if (t[START].tv_usec > t[END].tv_usec)
	{
		t[DIFF].tv_usec = (1e6 - t[END].tv_usec) + t[START].tv_usec;
	}
	else
	{
		t[DIFF].tv_usec = t[END].tv_usec - t[START].tv_usec;
	}
	
	return t[DIFF].tv_sec * 1e6 + t[DIFF].tv_usec;
}

/* Function: heatrelax() */
int heatrelax(int *argc, char *argv[])
{
	/* check programm call */
	if (3 != *argc && 4 != *argc){ 
		usage();
		return -1;
	}

	unsigned int i,j,k;

	//to enable runtime mesarument, please ensure that DEBUG equals 0
#if DEBUG == 0
	unsigned long runtime;
	double flops = 0;
	double flopsTot = 0;
#endif
	struct timeval time[3];

	unsigned int lower,upper;	

	unsigned int iterations = atoi(argv[1]);
	unsigned int viewsize = atoi(argv[2]);
	double heatval = 127.0f;		// => H
	
	if(4 == *argc){
	 	heatval = atof(argv[3]); 
	}	

	/* sanity check */
	if (iterations < 0 || iterations > 1e6){
		usage();
		return -2;
	}
	if (viewsize < 0 || viewsize > 1e6){
		usage();
		return -3;
	}
	if (heatval < 0.0f || heatval > 127.0f){
		usage();
		return -4;
	}
	
	lower = viewsize/4 + 2;
	upper = (3*viewsize)/4 + 2;

#if DEBUG == 1
	printf("#LowerHeat %d , UpperHeat %d \n",lower,upper);
#endif	
	
	/* adjust dimension to fit the desired size */
	unsigned int worksize = viewsize + 4;
	
	/* allocate memory */
	double **x, **xn, **swp;
	x = (double**) malloc(worksize * sizeof(double*));
	xn = (double**) malloc(worksize * sizeof(double*));

	for (i=0; i<worksize; i++){ 
		x[i] = (double*) malloc(worksize * sizeof(double));
	}
	for (i=0; i<worksize; i++){
		xn[i] = (double*) malloc(worksize * sizeof(double));
	}
	/* init arrays */
	for (i=0; i<worksize; i++){ 
		for (j=0; j<worksize; j++){
			x[i][j] = 0.0f;
			xn[i][j] = 0.0f;
		}
	}

	/* set initial state of heat spot */
	for (i = 2; i < worksize-2; i++){
		if((i >= lower) && (i <= upper)){
			x[2][i] = heatval;
		}
	}

	/* benchmark */
	gettimeofday(&time[START], NULL);
	for (k=0; k<iterations; k++)
	{
		/* keep heat source active */
		for (i=2; i<worksize-2; i++)
		{
			for (j=2; j<worksize-2; j++)
			{
				if ((j >= lower) && (j <=upper)){
					x[2][j] = heatval;
				}
			}
		}

		/* calculate next step */
		for (i=2; i<worksize-2; i++)
		{
			for (j=2; j<worksize-2; j++)
			{
				double *nv = &xn[i][j]; // next value
				double *cv = &x[i][j]; // current value
				double *cvd = &x[i+1][j]; // cv-down 
				double *cvu = &x[i-1][j]; // cv-up
				double *cvr = &x[i][j+1]; // cv-right
				double *cvl = &x[i][j-1]; // cv-left

				*nv = *cv + PHI * (-4.0f * *cv + *cvd + *cvu + *cvr + *cvl);
			}
		}

#if DEBUG == 1
		// uncomment to print output
		printf("#iteration: %i/%i\n",k+1,iterations);
		for (i=2; i<worksize-2; i++)
		{
			for (j=2; j<worksize-2; j++)
			{
				double *cv = &x[i][j]; // current value
				printf("%d %d %d %lf\n",k,(i-2),(j-2),*cv); //iteration-step,row,column,value
			}
			//printf("\n");
		}
		//usleep(1e3*5); // ms
#endif

		swp = xn;
		xn = x;
		x = swp;
		swp = NULL;
	}
	gettimeofday(&time[END], NULL);

	/* free memory */
	for (i=0; i<worksize; i++)
	{
		free(x[i]);
		free(xn[i]);
		//free(circle[i]);
	}

	free(x);
	free(xn);
	//free(circle);

#if DEBUG == 0
	runtime = calcRuntime(time);
	flopsTot = (7.0f*viewsize*viewsize*iterations);
	flops = flopsTot/((double)runtime/1e6);
	printf("%i %i %lf %lf %lf %lf\n",
		viewsize, iterations, (double)runtime/1e6, (double)runtime/1e6/iterations,flops,flopsTot);
#endif

	return 0;
}

int main(int argc, char *argv[])
{
	/* mpi setup  */
	//MPI_Status status;
	//MPI_Request request;
	char hostname[50];
	int nodes, rank;
	int retVal = 0; //Return Value.
	//int dst, src;
	
	MPI_Init(&argc, &argv);
	MPI_Comm_rank (MPI_COMM_WORLD, &rank);  // Who am I?
	MPI_Comm_size (MPI_COMM_WORLD, &nodes); // How many processes?
	gethostname(hostname, 50);

	printf("# I'm process %2d out of %2d (%s)\n", rank, nodes, hostname);
	MPI_Barrier(MPI_COMM_WORLD);
	
	if (rank == 0)
	{
		retVal = heatrelax(&argc, argv);
	}
	else
	{
		//do nothing node
	}

	printf("# %2d: done\n", rank);
	MPI_Finalize();

	return retVal;
}

